﻿/**
 @author Eric AMBROSI
 @alias ricko || rickota || ricota
 @classDescription allow to tween object in Silverlight 2
 @version 2.1.2 stable version
*/

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;


namespace Tweened 
{
    /// <summary>
    ///     Interface TweenDescription used to describe a new animated sequence, this interface is implemented by TweenColorDescription and TweenDoubleDescription. The object is then passed to the method AddSequences of a Tween.
    /// </summary>
    /// <seealso cref="Tween"/>
    public interface TweenDescription
    {
        /// <summary>
        /// Method proxy returning the current value of the property of xaml object lively.
        /// </summary>
        /// <returns>Returns <c>double</c> or <c>Color</c> depending the type of propety affected.</returns>
        TweenDescription Clear();

        /// <summary>
        /// Method proxy returning the current value of the property of xaml object lively.
        /// </summary>
        /// <returns>Returns <c>double</c> or <c>Color</c> depending the type of propety affected.</returns>
        object GetTweenedProperty();
       
        /// <summary>
        /// Method proxy setting the current value of the property of xaml object lively.
        /// </summary>
        /// <param name="value">Color or double value depending the type of property affected.</param>
        /// <returns>returns the old value of the property : <c>double</c> value or <c>Color</c> value depending the TweenedDescription passed.</returns>
        object SetTweenedProperty(object value);

        /// <summary>
        ///     Method setting the new end value, starting from actual value. You must define a new duration.
        /// </summary>
        /// <param name="value">Color or double value depending the type of propety affected.</param>
        /// <returns>returns the old value of the property : <c>double</c> value or <c>Color</c> value depending the TweenedDescription passed.</returns>
        object ContinueTo(object value);

        /// <summary>
        /// returns the actual property passed. You don't need to use that property.
        /// </summary>
        TweenedProperty TweenProperty { get; }
        
    }

    /// <summary>
    /// TweenDoubleDescription used to describe a new animated sequence. The TweenDoubleDescription instance is then passed to the AddSequence method.
    ///     <example>
    ///         <code>
    ///  //for tween a double type property 
    ///  TweenDoubleDescription tdc = new TweenDoubleDescription(myCanvas, TweenablePropertie.ScaleX, Equation.EaseOutElastic, 2, 2);
    /// 
    ///  myTween.AddSequence(tdc);
    ///  
    ///  myTween.Start();
    ///             </code>
    ///     </example>
    /// </summary>
    /// <seealso cref="Tween"/>
    /// 
    public class TweenDoubleDescription : TweenDescription
    {

        internal double Duration { get; set; }
        internal double EndValue { get; set; }
        internal double StartValue { get; set; } 

        /// <summary>
        ///     BeginTime can shift the start of animated Sequence based on a number of seconds
        ///     Example:
        ///     TweenDoubleDescription.BeginTime = TimeSpan.FromSeconds (2);
        ///     Can shift the start of the animation 2 seconds
        /// </summary>
        public TimeSpan? BeginTime { get { return aIn.BeginTime; } set { aIn.BeginTime = value; } }

        internal Func<double, double, double, double, double> TweenEquation;
        internal string TweenedObjectName;
        internal DoubleAnimationUsingKeyFrames aIn = new DoubleAnimationUsingKeyFrames();
        internal DoubleAnimationUsingKeyFrames aOut = new DoubleAnimationUsingKeyFrames();
        internal UIElement XamlObj;

        /// <summary>
        /// returns the actual property passed. You don't need to use that property.
        /// </summary>
        public TweenedProperty TweenProperty {get; set;}
        public double Definition;
       
        #region Constructors

        /// <summary>
        ///     TweenDoubleDescription used to describe a new animated sequence. The object is then passed to the method AddSequence.
        ///     <example>
        ///         <code>
        /// TweenDoubleDescription tdd = new TweenDoubleDescription(myCanvas, TweenablePropertie.ScaleX, Equation.EaseOutElastic, 2, 2);
        ///         </code>
        ///     </example>
        /// </summary>
        /// <param name="XamlObject">UIElement which has to be animated.</param>
        /// <param name="property">The property you want to assign</param>
        /// <param name="tweenEquation">The equation of animation desired, Equation.EaseOutElastic for example</param>
        /// <param name="duration">Animation Time expressed in seconds</param>
        /// <param name="endValue">The final value (double) of the property affected</param>
        /// <remarks>In this case you do not need to pass the start value. The start value is the current.</remarks>
        public TweenDoubleDescription(UIElement XamlObject, TweenedProperty property, Func<double, double, double, double, double> tweenEquation, double duration, double endValue)
        {

            this.XamlObj = XamlObject;
            this.TweenProperty = property;
            this.TweenEquation = tweenEquation;
            this.EndValue = endValue;
            this.Duration = duration;
            this.Definition = 50 * this.Duration;
            this.StartValue = (double)this.GetTweenedProperty();
            this.TweenedObjectName = this.XamlObj.GetValue(FrameworkElement.NameProperty).ToString();

            //ici on initialise les RenderTransform
            InitXaml();

            WriteSplineIn();
            WriteSplineOut();

        }

        /// <summary>
        ///     TweenObject used to describe a new animated sequence. The object is then passed to the method AddSequence.
        ///     <example>
        ///         <code>
        /// TweenDoubleDescription tdd = new TweenDoubleDescription(myCanvas, TweenablePropertie.X, Equation.EaseOutElastic, 2, 250, 28);
        ///         </code>
        ///     </example>
        /// </summary>
        /// <param name="XamlObject">UIElement which has to be animated.</param>
        /// <param name="property">The property you want to assign</param>
        /// <param name="tweenEquation">The equation of animation desired, Equation.EaseOutElastic for example</param>
        /// <param name="duration">Animation Time expressed in seconds</param>
        /// <param name="endValue">The final value(double) of the property affected</param>
        /// <param name="startValue">The start value(double) of the property affected, this parameter is optional.</param>
        public TweenDoubleDescription(UIElement XamlObject, TweenedProperty property, Func<double, double, double, double, double> tweenEquation, double duration, double endValue, double startValue)
        {
            this.XamlObj = XamlObject;
            this.TweenProperty = property;
            this.TweenEquation = tweenEquation;
            this.StartValue = startValue;
            this.EndValue = endValue;
            this.Duration = duration;
            this.Definition = 50 * this.Duration;

            //I initialyse RenderTransform here
            InitXaml();

            WriteSplineIn();
            WriteSplineOut();
        }

        #endregion

        /// <summary>
        ///     Clear method is used to remove contained animation in a TweenDescription object.
        ///     <example>
        ///         <code>
        /// TweenDoubleDescription tdd = new TweenDoubleDescription(myCanvas, TweenablePropertie.ScaleX, Equation.EaseOutElastic, 2, 2);
        /// tdd.Clear(); //remove the animation above        
        ///         </code>
        ///     </example>
        /// </summary>
        public TweenDescription Clear()
        {
            aIn = new DoubleAnimationUsingKeyFrames();
            aOut = new DoubleAnimationUsingKeyFrames();
            return this;
        }

        #region InitXaml :: on initialise les RenderTransform
        private void InitXaml()
        {
            PropertyType.SimpleType = this.XamlObj;

            TransformGroup TweenedTransformGroup = new TransformGroup();

            #region Ajout du noeud RenderTransform si besoin
            try
            {
                int childCount = (this.XamlObj.RenderTransform as TransformGroup).Children.Count;
                TweenedTransformGroup = (this.XamlObj.RenderTransform as TransformGroup);
            }
            catch (Exception e)
            {

                this.XamlObj.RenderTransformOrigin = new Point(0.5, 0.5);
                TweenedTransformGroup.Children.Add(new ScaleTransform());
                TweenedTransformGroup.Children.Add(new SkewTransform());
                TweenedTransformGroup.Children.Add(new RotateTransform());
                TweenedTransformGroup.Children.Add(new TranslateTransform());
                this.XamlObj.RenderTransform = TweenedTransformGroup;

            };
            #endregion
            
            #region Affectation dans tous les cas des Transforms
            (TweenedTransformGroup.Children as TransformCollection)[0] = PropertyType.ScaleType;
            (TweenedTransformGroup.Children as TransformCollection)[1] = PropertyType.SkewType;
            (TweenedTransformGroup.Children as TransformCollection)[2] = PropertyType.RotateType;
            (TweenedTransformGroup.Children as TransformCollection)[3] = PropertyType.TranslateType;
            #endregion

        }
        #endregion

        #region setTweenedProperty :: getter / setter d'une TweenedProperty

        /// <summary>
        ///     Method proxy returning the current value of the property of xaml object lively.
        /// </summary>
        /// <returns>Returns <c>double</c></returns>
        public object GetTweenedProperty()
        {
            
            TweenedType type = this.TweenProperty.TType;
            switch (type)
            {
                case TweenedType.Simple:
                    return this.XamlObj.GetValue(this.TweenProperty.AccessProp as DependencyProperty);
                             
                default:
                    return this.TweenProperty.PropType.GetValue((DependencyProperty)this.TweenProperty.AccessProp);
            }
        }

        /// <summary>
        ///     Method proxy setting the current value of the property of xaml object lively.
        ///     <exemple>
        ///         <code>
        /// myTweenObject.SetTweenedProperty( Color.FromArgb(255,68,145,64) );
        ///         </code>
        ///     </exemple>
        /// </summary>
        /// <param name="value"><c>Double</c> value.</param>
        /// <returns>returns the old <c>double</c> value of the property</returns>
        public object SetTweenedProperty(object value)
        {
            var oldValue = this.GetTweenedProperty();
            TweenedType type = this.TweenProperty.TType;
            switch (type)
            {
                case TweenedType.Simple:
                    this.XamlObj.SetValue(this.TweenProperty.AccessProp as DependencyProperty, value);
                    break;
                case TweenedType.RenderTransform:
                    this.TweenProperty.PropType.SetValue(this.TweenProperty.AccessProp as DependencyProperty, value);
                    break;
            }
            return oldValue;
        }
        #endregion

        #region Méthodes qui gèrent la création des Storyboards
        /// <summary>
        ///     Method setting the new end value, starting from actual value - don't use this method for RenderTransform animation. 
        ///     <exemple>
        ///         <code>
        /// myTweenObject.ContinueTo( 200 /* Width or Left for example */ );
        ///         </code>
        ///     </exemple>
        /// </summary>
        /// <param name="value"><c>Double</c> value.</param>
        /// <returns>returns the old value of the property : <c>double</c> value.</returns>
        public object ContinueTo(object value)
        {
            double oldValue = this.EndValue;
            TweenedType type = this.TweenProperty.TType;
            switch (type)
            {
                case TweenedType.Simple:
                    this.StartValue = (double)this.XamlObj.GetValue(this.TweenProperty.AccessProp as DependencyProperty);
                    this.EndValue = (double)value;
                    this.ReWriteSplineIn();
                    return oldValue;

                default:                
                    this.StartValue = (double)this.TweenProperty.PropType.GetValue(this.TweenProperty.AccessProp as DependencyProperty);
                    this.EndValue = (double)value;
                    this.ReWriteSplineIn();
                    //Debug.WriteLine("Value continue is :: " + this.StartValue + " - " + value + " - " + this.TweenProperty.PropType.GetValue(this.TweenProperty.AccessProp as DependencyProperty));
                    return oldValue;
                    
            }
            
        }

        private void ReWriteSplineIn()
        {
            //2 - calculer le changement
            double change = this.EndValue - this.StartValue;
            //3 - on calcule l'écart
            double ecart = Math.Round(this.Duration / this.Definition, 3);
            for (int i = 0; i < this.Definition; i++)
            {
                SplineDoubleKeyFrame sdkf = new SplineDoubleKeyFrame();
                double currentTime = ecart * i;
                (this.aIn.KeyFrames[i] as SplineDoubleKeyFrame).Value = (double)Math.Round(this.TweenEquation(currentTime, this.StartValue, change, this.Duration) * 1000) / 1000;
               
            }

        }

        private void WriteSplineIn()
        {
            Storyboard.SetTarget(this.aIn, this.XamlObj as DependencyObject);
            Storyboard.SetTargetProperty(this.aIn, new PropertyPath(this.TweenProperty.Prop));

            //2 - calculer le changement
            double change = this.EndValue - this.StartValue;

            //3 - on calcule l'écart
            double ecart = Math.Round(this.Duration / this.Definition, 3);

            //4 - on crée chaque keyFrame
            //Debug.WriteLine(this.Definition);
            for (int i = 0; i < this.Definition; i++)
            {
                SplineDoubleKeyFrame sdkf = new SplineDoubleKeyFrame();
                double currentTime = ecart * i;
                TimeSpan ts = new TimeSpan(0, 0, 0, 0, (int)(currentTime * 1000));
                sdkf.Value = (double)Math.Round(this.TweenEquation(currentTime, this.StartValue, change, this.Duration) * 1000) / 1000;
                sdkf.KeyTime = ts;
                this.aIn.KeyFrames.Add(sdkf);
                //Debug.WriteLine("kt = {0}, value = {1}", sdkf.KeyTime, sdkf.Value);
            }
        }

        private void WriteSplineOut()
        {
            Storyboard.SetTarget(this.aOut, this.XamlObj as DependencyObject);
            Storyboard.SetTargetProperty(this.aOut, this.TweenProperty.cheminProp);
            //2 - calculer le changement
            //var endvalue = (double)pTo.getTweenedPropertie();
            double change = this.EndValue - this.StartValue;

            //3 - on calcule l'écart
            double ecart = Math.Round(this.Duration / this.Definition, 3);

            //4 - on crée chaque keyFrame
            for (int i = 0; i < this.Definition; i++)
            {
                SplineDoubleKeyFrame sdkf = new SplineDoubleKeyFrame();
                double currentTime = ecart * i;
                TimeSpan ts = new TimeSpan(0, 0, 0, 0, (int)(currentTime * 1000));
                sdkf.Value = (double)Math.Round(this.TweenEquation(currentTime, this.EndValue, -change, this.Duration) * 1000) / 1000;
                sdkf.KeyTime = ts;
                this.aOut.KeyFrames.Insert(0, sdkf);
            }
        }
        #endregion
    }

     /// <summary>
    ///     TweenColorDescription used to define a new animated color sequence. The object is then passed to the method AddSequence of a Tween instance.
    ///     <example>
    ///         <code>
    ///  var myTween = new Tween();
    ///  TweenColorDescription tcd = new TweenColorDescription(myRectangle, TweenablePropertie.Fill, 2, Color.FromArgb(255, 20, 150, 68));
    ///  myTween.AddSequence(tcd);
    ///  myTween.Start();
    ///             </code>
    ///     </example>
    /// </summary>
    /// <seealso cref="Tween"/>
    /// 
    public class TweenColorDescription : TweenDescription
    {

        internal double Duration { get; set; }
        internal Color? EndColor { get; set; }
        internal Color? StartColor { get; set; }

        /// <summary>
        ///     BeginTime can shift the start of animated Sequence based on a number of seconds
        ///     Example:
        ///     TweenColorDescription.BeginTime = TimeSpan.FromSeconds (2);
        ///     Can shift the start of the animation 2 seconds
        /// </summary>
        public TimeSpan? BeginTime { get { return aIn.BeginTime; } set { aIn.BeginTime = value; } }

        internal ColorAnimation aIn = new ColorAnimation();
        internal ColorAnimation aOut = new ColorAnimation();
        internal Shape XamlObj;

        /// <summary>
        /// returns the actual property passed. You don't need to use that property.
        /// </summary>
        public TweenedProperty TweenProperty { get; set; }
       
        #region Constructors

        /// <summary>
        ///     TweenColorDescription is used to define new animated color sequence.
        ///     Used to animate the color for a Shape Fill and stroke.
        ///     <example>
        ///         <code>
        /// TweenColorDescription tcd = new TweenColorDescription(aCanvas, TweenablePropertie.Fill, 2, Color.FromArgb(255, 20, 150, 68));
        ///         </code>
        ///     </example>
        /// </summary>
        /// <param name="XamlShape">Shape which has to be color animated.</param>
        /// <param name="property">The property you want to assign</param>
        /// <param name="duration">Animation Time expressed in seconds</param>
        /// <param name="endColor">The final value(Color) of the property affected</param>
        /// <remarks>
        ///     There is no need to specify equation in this case.
        /// </remarks>
        public TweenColorDescription(Shape XamlShape, TweenedProperty property, double duration, Color? endColor)
        {
            this.XamlObj = XamlShape;
            this.TweenProperty = property;
            this.StartColor = (Color?)this.GetTweenedProperty();
            this.EndColor = endColor;
            this.Duration = duration;

            WriteLinearIn();
            WriteLinearOut();
        }

        /// <summary>
        ///     TweenColorDescription is used to define new animated color sequence.
        ///     Used to animate the color for a Shape Fill and stroke.
        ///     <example>
        ///         <code>
        /// TweenColorDescription tcd = TweenColorDescription TweenObject(aCanvas, TweenablePropertie.Fill, 2, Color.FromArgb(255, 20, 150, 68));
        ///         </code>
        ///     </example>
        /// </summary>
        /// <param name="XamlShape">Shape which has to be color animated.</param>
        /// <param name="property">The property you want to assign</param>
        /// <param name="duration">Animation Time expressed in seconds</param>
        /// <param name="endColor">The final value(Color) of the property affected</param>
        /// <param name="startColor">The start value(Color) of the property affected</param>
        /// <remarks>
        ///     There is no need to specify equation in this case.
        /// </remarks>
        public TweenColorDescription(Shape XamlShape, TweenedProperty property, double duration, Color? endColor, Color? startColor)
        {
            this.XamlObj = XamlShape;
            this.TweenProperty = property;
            this.StartColor = startColor;
            this.EndColor = endColor;
            this.Duration = duration;

            WriteLinearIn();
            WriteLinearOut();
        } 
        #endregion

        /// <summary>
        ///     Clear method is used to remove contained animation in a TweenDescription object.
        ///     <example>
        ///         <code>
        /// TweenColorDescription tcd = new TweenColorDescription(myCanvas, TweenablePropertie.ScaleX, Equation.EaseOutElastic, 2, 2);
        /// tcd.Clear(); //remove the animation above        
        ///         </code>
        ///     </example>
        /// </summary>
        public TweenDescription Clear()
        {
            aIn = new ColorAnimation();
            aOut = new ColorAnimation();
            return this;
        }
        
        #region setTweenedProperty :: getter / setter d'une TweenedProperty

        /// <summary>
        ///     Method proxy returning the current value of the property of xaml object lively.
        /// </summary>
        /// <returns>Returns <c>Color</c>.</returns>
        public object GetTweenedProperty()
        {
            
            TweenedType type = this.TweenProperty.TType;
            SolidColorBrush scb = (SolidColorBrush)this.XamlObj.GetValue(this.TweenProperty.AccessProp as DependencyProperty);
            return (object)scb.Color;

        }

        /// <summary>
        ///     Method proxy setting the current value of the property of shape object lively.
        ///     <exemple>
        ///         <code>
        ///         myTweenColorDescription.SetTweenedProperty( Color.FromArgb(255,68,145,64) );
        ///         </code>
        ///     </exemple>
        /// </summary>
        /// <param name="value">Color value.</param>
        /// <returns>returns the old value of the property : <c>double</c> value or <c>Color</c> value depending the tweenedObject</returns>
        public object SetTweenedProperty(object value)
        {
            var oldValue = this.GetTweenedProperty();
            this.XamlObj.SetValue(this.TweenProperty.AccessProp as DependencyProperty, value);
            return oldValue;
        }
        #endregion

        #region Méthodes qui gèrent la création des Storyboards
        /// <summary>
        ///     Method setting the new end value, setting start value from actual value.
        ///     <exemple>
        ///         <code>
        ///         
        /// myTweenColorDescription.ContinueTo( Color.FromArgb(255,68,145,64) /* Fill or Stroke */);
        /// //then you have to invoke the Begin method from the Tween object wich actually contains myTweenColorDescription
        /// myTween.Begin();
        ///         </code>
        ///     </exemple>
        /// </summary>
        /// <param name="value">Color value.</param>
        /// <returns>returns the old <c>Color</c> value of the property Fill or Stroke.</returns>
        public object ContinueTo(object value)
        {
            Color oldValue = (Color)this.aIn.To;
            this.aIn.From = this.StartColor = (Color)this.GetTweenedProperty(); ;
            this.aIn.To = this.EndColor = (Color)value;
            return oldValue;
        }

        private void WriteLinearIn()
        {
            Storyboard.SetTarget(this.aIn, this.XamlObj as DependencyObject);
            Storyboard.SetTargetProperty(this.aIn, new PropertyPath(this.TweenProperty.Prop));
            this.aIn.To = this.EndColor;
            this.aIn.From = this.StartColor;
        }
        private void WriteLinearOut()
        {
            Storyboard.SetTarget(this.aOut, this.XamlObj as DependencyObject);
            Storyboard.SetTargetProperty(this.aOut, new PropertyPath(this.TweenProperty.Prop));
            this.aOut.To = this.StartColor;
            this.aOut.From = this.EndColor;
        }
        #endregion
    }
}
